package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListSerivce;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import jodd.util.StringUtil;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.swing.*;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ListSerivceImpl implements ListSerivce {

    @Resource
    private RestHighLevelClient restHighLevelClient;
    @Resource
    private GoodsDao goodsDao;
    @Resource
    private ProductFeignClient productFeignClient;
    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public void upperGoods(Long skuId) {
        //一部手机全部索引手机
        Goods goods = new Goods();

        //1、根据SKUID查询skuinfo信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setCreateTime(new Date());
        //2、根据tmid查询品牌相关数据
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(skuInfo.getTmId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //3、根据category3id查询分类视图
        BaseCategoryView baseCategoryView = productFeignClient.getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //4：根据skuId 查询 sku_attr_value   base_attr_info  base_attr_value
        List<SkuAttrValue> skuAttrValueList = productFeignClient.getSkuAttrValue(skuId);

        //偷天换日  stream().map().collect(Collectors.toList())
        List<SearchAttr> searchAttrList = skuAttrValueList.stream().map(SkuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();//goods中接收SearchAttr型，新建对象，放值
            searchAttr.setAttrId(SkuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(SkuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(SkuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);

        //保存索引库
        goodsDao.save(goods);
    }

    //删除索引
    @Override
    public void lowerGoods(Long skuId) {
/*      ES官方高级客户端的删除方式
        DeleteRequest deleteRequest = new DeleteRequest();
        deleteRequest.index("goods");//索引库名称
        deleteRequest.id(skuId.toString());
        try {
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
*/
        //springdataES  删除方式    mybatis-plus相同
        goodsDao.deleteById(skuId);
    }

    //增加热度、评分
    @Override
    public void hotScore(Long skuId) {
        //通过缓存进行缓冲，n次后更新一次ES
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, 1);

        if (hotScore%10 == 0){
            //1、查询ES库，获取商品评分
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            //2、评分加分
            goods.setHotScore(Math.round(hotScore));
            //3、保存回ES库
            goodsDao.save(goods);
        }

    }

    //开始搜索
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        SearchResponseVo vo = null;
        try {
            //1、构建搜索请求对象
            SearchRequest searchRequest = buildSearchRequest(searchParam);
            //2、执行搜索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //3、解析结果对象
            vo = parseSearchResponse(searchResponse);
            vo.setPageNo(searchParam.getPageNo());//当前页
            vo.setPageSize(searchParam.getPageSize());//每页数
            //总页数 == （总条数 + 每页数 -1)/每页数
            vo.setTotalPages((vo.getTotal()+vo.getPageSize()-1)/vo.getPageSize());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return vo;
    }

    /**3、解析结果对象*/
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();

        //获取聚合对象    string：id   Aggregation：sub子
        Map<String, Aggregation> stringAggregationMap = searchResponse.getAggregations().asMap();

        //1、品牌集合解析
        //Aggregation接口，parse**terms为该接口的实现类，方法较多
        ParsedLongTerms aggTmId = (ParsedLongTerms) stringAggregationMap.get("aggTmId");//接收long类型的聚合
        //List<SearchResponseTmVo> trademarkList 需要的属性
        List<SearchResponseTmVo> trademarkList = aggTmId.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            tmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));//parseLong
            //品牌名称
            ParsedStringTerms aggTmName = ((Terms.Bucket) bucket).getAggregations().get("aggTmName");//接收string类型的聚合
            tmVo.setTmName(aggTmName.getBuckets().get(0).getKeyAsString());
            //品牌logo
            ParsedStringTerms aggTmLogoUrl = ((Terms.Bucket) bucket).getAggregations().get("aggTmLogoUrl");
            tmVo.setTmLogoUrl(aggTmLogoUrl.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);

        //1、平台属性解析
        ParsedNested aggAttrs = (ParsedNested) stringAggregationMap.get("aggAttrs");
        //嵌套可能包含多层嵌套，故无buckets方法，获得Aggregations聚合
        ParsedLongTerms aggAttrId = aggAttrs.getAggregations().get("aggAttrId");
        List<SearchResponseAttrVo> attrsList = aggAttrId.getBuckets().stream().map(bucket->{
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性id
            attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));

            ParsedStringTerms aggAttrName = ((Terms.Bucket) bucket).getAggregations().get("aggAttrName");
            attrVo.setAttrName(aggAttrName.getBuckets().get(0).getKeyAsString());

            ParsedStringTerms aggAttrValue = ((Terms.Bucket) bucket).getAggregations().get("aggAttrValue");

            List<String> attrValueList = aggAttrValue.getBuckets().stream().map(
                    Terms.Bucket::getKeyAsString  //lamda直接返回简写     terms.原因bucket为terms内部类
            ).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);

            return attrVo;
        }).collect(Collectors.toList());

        vo.setAttrsList(attrsList);
        //2、商品结果集
        SearchHit[] hits1 = hits.getHits();
        /*List<Goods> goodsList = Arrays.stream(hits1).map(hit -> {
            //将查询数据转换为Goods的json对象
            return JSONObject.parseObject(hit.getSourceAsString(), Goods.class);
        }).collect(Collectors.toList());*/
       //2分页
        vo.setTotal(hits.getTotalHits());
        System.out.println(hits.getTotalHits());

        //3、高亮hists--hits[]--[]
        List<Goods> goodsList = Arrays.stream(hits1).map(hit->{
            Goods goods = JSONObject.parseObject(hit.getSourceAsString(), Goods.class);
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields != null && highlightFields.size() > 0) {
                HighlightField title = highlightFields.get("title");
                if (title != null) {
                    String hname = title.fragments()[0].toString();
                    goods.setTitle(hname);
                }
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);

        return vo;
    }

    /**1、构建搜索请求对象*/
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //条件对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //搜索全部数据    searchSourceBuilder.query(new MatchAllQueryBuilder());
        //组合条件对象即{}    查询多个条件使用，query会被覆盖
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //1、搜索关键词       matchQuery模糊查询，分词器
        String keyword = searchParam.getKeyword();
        if (!StringUtil.isEmpty(keyword)){
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword));
        }else {//搜索全部数据
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());//must & filter无区别
        }

        //2、品牌 过滤条件  品牌id：品牌名     termQuery精确查询
        String trademark = searchParam.getTrademark();
        if (!StringUtil.isEmpty(trademark)){

            String[] split = StringUtil.split(trademark, ":");//切割出品牌id做条件
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
        }

        //3、一二三级分类
        Long category1Id = searchParam.getCategory1Id();
        if (category1Id != null){
            boolQueryBuilder.must(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if (category2Id != null){
            boolQueryBuilder.must(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if (category3Id != null){
            boolQueryBuilder.must(QueryBuilders.termQuery("category3Id",category3Id));
        }

        //4、平台属性id：平台属性值：平台属性名称
        String[] props = searchParam.getProps();//页面提交的数组
        if (props != null && props.length > 0){//数组不可使用stream()
            //父：多种属性的list
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            for (String prop : props) {
                String[] split = prop.split(":");//[attrId,attrValue,attrName]
                //子：父中的每种属性
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));//attrName可根据id获得
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));//该属性的多种可能
                //放置多个子进父
                boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
            }
            //放置父到最大查询范围
            boolQueryBuilder.filter(boolQuery);
        }

        //5、分页      非筛选查询条件，无需使用bool
        Integer pageNo = searchParam.getPageNo();//当前页
        Integer pageSize = searchParam.getPageSize();//每页显示
        searchSourceBuilder.from((pageNo-1)*pageSize);//开始行
        searchSourceBuilder.size(pageSize);

        //6、排序      order --> 0:asc
        String order = searchParam.getOrder();
        if (!StringUtil.isEmpty(order)) {
            String[] split = order.split(":");
            String field = null;
            switch (split[0]){
                case "1": field = "hotScore";//综合排序(热度)
                case "2": field = "price";//价格排序
            }
            //sort(别名，排序方式asc/desc)
            searchSourceBuilder.sort(field,
                    split[1].equalsIgnoreCase(SortOrder.ASC.toString())
                            ?SortOrder.ASC:SortOrder.DESC);
        }
        //7、高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                .field("title")//高亮的内容
                .preTags("<span style='color:red'>")//内容前添加
                .postTags("</span>");//内容后添加
        searchSourceBuilder.highlighter(highlightBuilder);

        //8、聚合
        //① 品牌聚合  聚合别名解析时使用     筛选列表中品牌
        searchSourceBuilder.aggregation(
                //通过id固定名称和图片，父id下名称与图片并列
                AggregationBuilders.terms("aggTmId").field("tmId")
                    .subAggregation(AggregationBuilders.terms("aggTmName").field("tmName"))
                    .subAggregation(AggregationBuilders.terms("aggTmLogoUrl").field("tmLogoUrl")));
        //② 平台属性聚合  筛选列表
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("aggAttrs","attrs")
                .subAggregation(AggregationBuilders.terms("aggAttrId").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("aggAttrName").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("aggAttrValue").field("attrs.attrValue"))));

        //9、将组合条件对象放置入条件对象
        searchSourceBuilder.query(boolQueryBuilder);

        SearchRequest searchRequest = new SearchRequest();//搜索请求对象  请求ES
        //设置要搜索的索引库名
        searchRequest.indices("goods");//MySQL库     GET goods/_search
        //searchRequest.types("info");//MySQL表    当前库内仅一张表，可不设置表
        //构建搜索条件对象      searchsourcebuilder：ES语句中最大的{}
        searchRequest.source(searchSourceBuilder);

        //打印DSL语句
        System.out.println(searchSourceBuilder.toString());

        return searchRequest;
    }
}
